/* Copyright (c) 2010, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.logisim.file;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.cburch.logisim.circuit.Circuit;
import com.cburch.logisim.circuit.SubcircuitFactory;
import com.cburch.logisim.comp.Component;
import com.cburch.logisim.comp.ComponentFactory;
import com.cburch.logisim.tools.AddTool;
import com.cburch.logisim.tools.Library;
import com.cburch.logisim.tools.Tool;

public class FileStatistics {
    public static class Count {
        private Library library;
        private ComponentFactory factory;
        private int simpleCount;
        private int uniqueCount;
        private int recursiveCount;

        private Count(ComponentFactory factory) {
            this.library = null;
            this.factory = factory;
            this.simpleCount = 0;
            this.uniqueCount = 0;
            this.recursiveCount = 0;
        }

        public Library getLibrary() {
            return library;
        }

        public ComponentFactory getFactory() {
            return factory;
        }

        public int getSimpleCount() {
            return simpleCount;
        }

        public int getUniqueCount() {
            return uniqueCount;
        }

        public int getRecursiveCount() {
            return recursiveCount;
        }
    }

    public static FileStatistics compute(LogisimFile file, Circuit circuit) {
        Set<Circuit> include = new HashSet<Circuit>(file.getCircuits());
        Map<Circuit,Map<ComponentFactory,Count>> countMap;
        countMap = new HashMap<Circuit,Map<ComponentFactory,Count>>();
        doRecursiveCount(circuit, include, countMap);
        doUniqueCounts(countMap.get(circuit), countMap);
        List<Count> countList = sortCounts(countMap.get(circuit), file);
        return new FileStatistics(countList, getTotal(countList, include),
                getTotal(countList, null));
    }

    private static Map<ComponentFactory,Count> doRecursiveCount(Circuit circuit,
            Set<Circuit> include,
            Map<Circuit,Map<ComponentFactory,Count>> countMap) {
        if (countMap.containsKey(circuit)) {
            return countMap.get(circuit);
        }

        Map<ComponentFactory,Count> counts = doSimpleCount(circuit);
        countMap.put(circuit, counts);
        for (Count count : counts.values()) {
            count.uniqueCount = count.simpleCount;
            count.recursiveCount = count.simpleCount;
        }
        for (Circuit sub : include) {
            SubcircuitFactory subFactory = sub.getSubcircuitFactory();
            if (counts.containsKey(subFactory)) {
                int multiplier = counts.get(subFactory).simpleCount;
                Map<ComponentFactory,Count> subCount;
                subCount = doRecursiveCount(sub, include, countMap);
                for (Count subcount : subCount.values()) {
                    ComponentFactory subfactory = subcount.factory;
                    Count supercount = counts.get(subfactory);
                    if (supercount == null) {
                        supercount = new Count(subfactory);
                        counts.put(subfactory, supercount);
                    }
                    supercount.recursiveCount += multiplier * subcount.recursiveCount;
                }
            }
        }

        return counts;
    }

    private static Map<ComponentFactory,Count> doSimpleCount(Circuit circuit) {
        Map<ComponentFactory,Count> counts;
        counts = new HashMap<ComponentFactory,Count>();
        for (Component comp : circuit.getNonWires()) {
            ComponentFactory factory = comp.getFactory();
            Count count = counts.get(factory);
            if (count == null) {
                count = new Count(factory);
                counts.put(factory, count);
            }
            count.simpleCount++;
        }
        return counts;
    }

    private static void doUniqueCounts(Map<ComponentFactory,Count> counts,
            Map<Circuit,Map<ComponentFactory,Count>> circuitCounts) {
        for (Count count : counts.values()) {
            ComponentFactory factory = count.getFactory();
            int unique = 0;
            for (Circuit circ : circuitCounts.keySet()) {
                Count subcount = circuitCounts.get(circ).get(factory);
                if (subcount != null) {
                    unique += subcount.simpleCount;
                }
            }
            count.uniqueCount = unique;
        }
    }

    private static List<Count> sortCounts(Map<ComponentFactory,Count> counts,
            LogisimFile file) {
        List<Count> ret = new ArrayList<Count>();
        for (AddTool tool : file.getTools()) {
            ComponentFactory factory = tool.getFactory();
            Count count = counts.get(factory);
            if (count != null) {
                count.library = file;
                ret.add(count);
            }
        }
        for (Library lib : file.getLibraries()) {
            for (Tool tool : lib.getTools()) {
                if (tool instanceof AddTool) {
                    ComponentFactory factory = ((AddTool) tool).getFactory();
                    Count count = counts.get(factory);
                    if (count != null) {
                        count.library = lib;
                        ret.add(count);
                    }
                }
            }
        }
        return ret;
    }

    private static Count getTotal(List<Count> counts, Set<Circuit> exclude) {
        Count ret = new Count(null);
        for (Count count : counts) {
            ComponentFactory factory = count.getFactory();
            Circuit factoryCirc = null;
            if (factory instanceof SubcircuitFactory) {
                factoryCirc = ((SubcircuitFactory) factory).getSubcircuit();
            }
            if (exclude == null || !exclude.contains(factoryCirc)) {
                ret.simpleCount += count.simpleCount;
                ret.uniqueCount += count.uniqueCount;
                ret.recursiveCount += count.recursiveCount;
            }
        }
        return ret;
    }

    private List<Count> counts;
    private Count totalWithout;
    private Count totalWith;

    private FileStatistics(List<Count> counts, Count totalWithout,
            Count totalWith) {
        this.counts = Collections.unmodifiableList(counts);
        this.totalWithout = totalWithout;
        this.totalWith = totalWith;
    }

    public List<Count> getCounts() {
        return counts;
    }

    public Count getTotalWithoutSubcircuits() {
        return totalWithout;
    }

    public Count getTotalWithSubcircuits() {
        return totalWith;
    }
}
